package eu.ioc.factory;

import java.lang.reflect.Method;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.Element;

import eu.ioc.customize.SingletonClass;
import eu.ioc.customize.StaticClass;

/**
 * 负责创建和初始化Bean实例
 * @author wzz
 *
 */
abstract class BeanMaker {
	
	/**
	 * 创建一个实例
	 * @param classIdentity 完整类名
	 * @param initPara 初始化参数
	 * @return 一个实例或当该类实现了eu.ioc.customize.StaticClass
	 * @throws ClassNotFoundException
	 * @throws CreateBeanException
	 */
	public static Object createBean(String classIdentity, Object... initPara) throws ClassNotFoundException, CreateBeanException{
		Class<? extends Object> _class = Class.forName(classIdentity);
		boolean isFinal = false;
		boolean isStatic = false;
		for (Class<?> superClass : _class.getInterfaces()) {
			if (superClass.equals(StaticClass.class))
				isStatic = true;
			if (superClass.equals(SingletonClass.class))
				isFinal = true;
		}
		if (isStatic && !isFinal)
			return null;
		Object instance = null;
		if (isFinal)
			try {
				instance = _class.getMethod("getInstance").invoke(null);
			} catch (Exception e) {
				throw new CreateBeanException("exception occurs while invoking getInstance", e);
			}
		else {
			Class<?>[] clazzs = new Class<?>[initPara.length];
			for (int i=0; i<initPara.length; i++)
				clazzs[i] = initPara[i].getClass();
			try {
				instance = _class.getConstructor(clazzs).newInstance(initPara);
			} catch (Exception e) {
				throw new CreateBeanException("exception occurs while create new instance", e);
			} 
		}
		return instance;
	}
	
	/**
	 * 设置属性
	 * @param clazz 类
	 * @param instance 实例，仅当类是一个静态类的时候可为空
	 * @param proEle property元素
	 * @throws CreateBeanException
	 */
	public static void setProperty(Class<?> clazz, Object instance, Element proEle) throws CreateBeanException {
		String name = proEle.attributeValue("name");
		if (name == null)
			throw new CreateBeanException("there must be a name for property");
		if (name.contains(".")) {
			String[] fields = name.split("\\.");
			name = fields[fields.length-1];
			StringBuffer path = new StringBuffer(fields[0]);
			for (int i=1; i < fields.length-1; i++) {
				path.append('.');
				path.append(fields[i]);
			}
			instance = getValue(path.toString(), instance, clazz);
			if (instance == null)
				throw new CreateBeanException("sub instance is null");
			clazz = instance.getClass();
		}
		String setMethod = "set" + Character.toUpperCase(name.charAt(0))
				+ ((name.length()>1)?name.substring(1):"");
		try {
			Object para =  getPropertyValue(proEle);
			for (Method method : clazz.getMethods()) {
				if(method.getName().equals(setMethod) &&
						method.getParameterTypes().length == 1) {
					method.invoke(instance, para);
				}
			}
		} catch (Exception e) {
			throw new CreateBeanException("exception occurs while invoking "+setMethod, e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public static Object getPropertyValue(Element proEle) throws ClassNotFoundException, CreateBeanException {
		List<Element> valueSec = proEle.elements();
		if (valueSec.size() <= 0)
			throw new CreateBeanException("can't find any property setting");
		Element setEle = valueSec.get(0);
		String name = setEle.getName();
		if ("ref".equals(name)) 
			return resolveRefElement(setEle);
		if ("value".equals(name))
			return resolveValueElement(setEle);
		if ("collection".equals(name))
			return resolveCollectionElement(setEle);
		if ("link".equals(name))
			return resolveLinkedElement(setEle);
		return null;
	}
	
	
	public static Object resolveValueElement(Element ele) throws ClassNotFoundException, CreateBeanException {
		String clazz = ele.attributeValue("class");
		if (clazz == null)
			clazz = "java.lang.String";
		String iniPara = ele.getText();
		if (iniPara == null || iniPara.length() < 1)
			return createBean(clazz);
		return createBean(clazz, ele.getText());
	}
	
	@SuppressWarnings("unchecked")
	public static Object resolveCollectionElement(Element setEle) throws ClassNotFoundException, CreateBeanException {
		String clazz = setEle.attributeValue("class");
		if (clazz == null)
			clazz = "java.util.LinkedList";
		Object collection = createBean(clazz);
		if (collection instanceof java.util.Map){
			java.util.Map<Object, Object> map = (java.util.Map<Object, Object>)collection;
			for (Element ele : (List<Element>)setEle.elements("value")) {
				map.put(ele.attributeValue("key"), resolveValueElement(ele));
			}
			return map;
		}
		if (collection instanceof java.util.Collection){
			java.util.Collection<Object> map = (java.util.Collection<Object>)collection;
			for (Element ele : (List<Element>)setEle.elements("value")) {
				map.add(resolveValueElement(ele));
			}
			return map;
		}
		return null;
	}
	
	public static Object resolveLinkedElement(Element setEle) throws ClassNotFoundException, CreateBeanException {
		String id = setEle.attributeValue("id");
		String name = setEle.attributeValue("name");
		if (name == null)
			throw new CreateBeanException("link element must have a name attribute");
		if (id != null) {
			Object instance = getRefBeanById(id, setEle.getParent().getParent());
			Class<?> clazz = getClassById(id, setEle.getParent().getParent());
			return getValue(name, instance, clazz);
		}
		String clazz = setEle.attributeValue("class");
		if (clazz != null)
			return getValue(name, getRefBeanById(id, setEle.getParent().getParent()), Class.forName(clazz));
		return null;
	}
	
	public static Object resolveRefElement(Element setEle) throws CreateBeanException, ClassNotFoundException{
		String id = setEle.attributeValue("id");
		if (id == null)
			throw new CreateBeanException("ref element must have a id attribute");
		return getRefBeanById(id, setEle.getParent());
	}
	
	public static Object getRefBeanById(String id, Element self) throws ClassNotFoundException, CreateBeanException {
		Element refBeanEle = null;
		if (id.contains(".")) 
			refBeanEle = locateBean(self.getDocument(), id);
		else {
			refBeanEle = (Element) self.selectSingleNode("../bean[@id='"+id+"']");
			if (refBeanEle == null)
				refBeanEle = locateBean(self.getDocument(), id);
		}
		if (refBeanEle == null)
			throw new CreateBeanException("ref bean can't be found with id:"+id);
		Object instance = BeanMaker.createBean(refBeanEle.attributeValue("class"));
		if (instance == null)
			return null;
		for (Object beanEles : refBeanEle.selectNodes("property")) {
			Element refProEle = (Element) beanEles;
			BeanMaker.setProperty(instance.getClass(), instance, refProEle);
		}
		return instance;
	}
	
	public static Class<?> getClassById(String id, Element self) throws CreateBeanException, ClassNotFoundException {
		Element refBeanEle = null;
		if (id.contains(".")) 
			refBeanEle = locateBean(self.getDocument(), id);
		else {
			refBeanEle = (Element) self.selectSingleNode("../bean[@id='"+id+"']");
			if (refBeanEle == null)
				refBeanEle = locateBean(self.getDocument(), id);
		}
		if (refBeanEle == null)
			throw new CreateBeanException("ref element can't be found with id:"+id);
		String clazz = refBeanEle.attributeValue("class");
		return Class.forName(clazz);
	}
	
	public static Element locateBean(Document xml ,String path) {
		String[] fields = path.split("\\.");
		StringBuffer buffer = new StringBuffer("/beans");
		for (int i=0; i<fields.length; i++) {
			if (i == fields.length-1) {
				buffer.append("/bean[@id='");
				buffer.append(fields[i]);
				buffer.append("']");
			} else {
				buffer.append("/package[@name='");
				buffer.append(fields[i]);
				buffer.append("']");
			}
		}
		return (Element) xml.selectSingleNode(buffer.toString());
	}
	
	/**
	 * 处理数据实体,临时实体将优于全局实体
	 * @throws CreateBeanException 
	 */
	public static Object getValue(String path, Object instance,Class<?> clazz) throws CreateBeanException{
		path = path.trim();
		String[] fields = path.split("\\.");
		// 迭代获取字段
		for (String field : fields) {
			Method method = null;
			// 使用get方法获取内部变量
			String methodName = "get" + Character.toUpperCase(field.charAt(0))
					+ ((field.length() > 1) ? field.substring(1) : "");
			try {
				method = clazz.getMethod(methodName);
				instance = method.invoke(instance);
				if (instance == null)
					return null;
				clazz = instance.getClass();
			} catch (Exception e) {
				throw new CreateBeanException(e);
			}
		}
		return instance;
	}
}
